/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                           *
 *                          Copyright (c) 2010                               *
 *                  Alen Stojanov [ ioi.alen@gmail.com ]                     *
 *                Jacobs University Bremen, Bremen, Germany                  *
 *                                                                           *
 *                          All Rights Reserved.                             *
 *                                                                           *
 *    Permission to use, copy, modify and distribute this software and       *
 *    its documentation is hereby granted, provided that both the            *
 *    copyright notice and this permission notice appear in all copies of    *
 *    the software, derivative works or modified versions, and any           *
 *    portions thereof, and that both notices appear in supporting           *
 *    documentation.                                                         *
 *                                                                           *
 *    This code is distributed in the hope that it will be useful, but       *
 *    WITHOUT ANY WARRANTY; without even the implied warranty of             *
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS       *
 *    DISCLAIM ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER          *
 *    RESULTING FROM THE USE OF THIS SOFTWARE.                               *
 *                                                                           *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <QtGui>
#include <QtOpenGL>
#include <QColor>
#include <cfloat>
#include <algorithm>
#include <iostream>

#include "gllspgraph.h"
#include "glpreview.h"
#include "tsdata.h"
#include "grahamscan.h"

using namespace std;

GLLSPGraph::GLLSPGraph(QWidget *parent)
{
	isInitialized = false;
	marginSize = 50.0;

	rangeMinX = DBL_MAX;
	rangeMaxX = DBL_MIN;
	rangeMinY = DBL_MAX;
	rangeMaxY = DBL_MIN;

	dataRadius = 4;
	zoom	   = 0.9;
	centerX	   = 0;
	centerY	   = 0;

	// glProjectionList = glGenLists(1);
}

void GLLSPGraph::on_addSeries (QVector<TSData*> series)
{
	if (series.size() > 0)
	{
		/* add the new time series into the vector */
		mutex.lock();
		data += series;
		mutex.unlock();
	}
}

void GLLSPGraph::on_updateControlPointsSize(int controlPointsSize)
{
	mutex.lock();

	/* calculate LSP projection */
	LSP lsp(data);
	projection  = lsp.calculate(controlPointsSize);
	connections = lsp.getConnections();

	for ( int i = 0; i < projection.size(); i++ ) {
		rangeMaxX = ( projection[i].getX() > rangeMaxX ) ? projection[i].getX() : rangeMaxX;
		rangeMinX = ( projection[i].getX() < rangeMinX ) ? projection[i].getX() : rangeMinX;
		rangeMaxY = ( projection[i].getY() > rangeMaxY ) ? projection[i].getY() : rangeMaxY;
		rangeMinY = ( projection[i].getY() < rangeMinY ) ? projection[i].getY() : rangeMinY;
	}

	mutex.unlock();

	if ( projection.size() > 0 ) {
		isInitialized = true;
	}

	this->update();

}

void GLLSPGraph::initializeGL()
{
	/* load a white color on the screen */
	qglClearColor(QColor::fromRgb(0, 0, 0));

	/* enable blending */
	glEnable (GL_BLEND);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	/* enable line-antialiasing */
	glEnable(GL_LINE_SMOOTH);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
}

void GLLSPGraph::paintGL()
{
	/* clear the projection matrix */
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	/* get the viewport */
	QSize viewport = this->size();
	double h = viewport.height();
	double w = viewport.width();

	/* prepare the viewport in case of no data */
	/* and print the default message on the screen */
	if ( data.size() == 0 || projection.size() == 0 ) {
		gluOrtho2D(0, w, 0, h);
		glMatrixMode(GL_MODELVIEW);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glLoadIdentity();
		glColor3f(1.0f, 1.0f, 1.0f);
		this->renderText( w / 2 - 200, h / 2, 0, "The Main Graph has not been loaded with data");
		glFlush();
	} else {


		/* determine the orthogonal projection scale */
		scale = ( w > h ) ? (rangeMaxY - rangeMinY) / h : (rangeMaxX - rangeMinX) / w;

		/* determine the extreme values of the projection */
		minX = (rangeMaxX + rangeMinX) / 2 - w * scale / 2 + centerX;
		maxX = (rangeMaxX + rangeMinX) / 2 + w * scale / 2 + centerX;
		minY = (rangeMaxY + rangeMinY) / 2 - h * scale / 2 + centerY;
		maxY = (rangeMaxY + rangeMinY) / 2 + h * scale / 2 + centerY;

		gluOrtho2D(minX, maxX, minY, maxY);
		glMatrixMode(GL_MODELVIEW);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glLoadIdentity();

		/* scale the whole projection by a coefficient */
		glScalef(zoom, zoom, zoom);
		glPushMatrix();

		for ( int i = 0; i < connections.size(); i++) {
			double x1 = projection[i].getX();
			double y1 = projection[i].getY();
			if ( connections[i] != i ) {
				double x2 = projection[connections[i]].getX();
				double y2 = projection[connections[i]].getY();
				glColor3f(1.0f, 1.0f, 1.0f);
				glBegin(GL_LINES);
				glVertex3f(x1, y1, 0.0f); // origin of the line
				glVertex3f(x2, y2, 0.0f); // ending point of the line
				glEnd( );
			}
		}
		for ( int i = 0; i < projection.size(); i++ ) {
			QColor color = data[i]->getColor();
			double x1 = projection[i].getX();
			double y1 = projection[i].getY();
			double radius = dataRadius * scale;

			glColor3f(color.redF(), color.greenF(), color.blueF());
			glBegin(GL_TRIANGLE_FAN);
			glVertex2f(x1, y1);
			for (int angle = 0;  angle < 360; angle++) {
				glVertex2f(x1 + sin(angle) * radius, y1 + cos(angle) * radius);
			}
			glEnd();
		}
		if ( convex_hull.size() > 0 ) {
			glEnable (GL_LINE_STIPPLE);
			glLineStipple (1, 0x0FF0);   /*  dashed   */
			glColor3f(1.0f, 1.0f, 1.0f);
			glBegin(GL_LINE_LOOP);
			for ( int i = 0; i < convex_hull.size(); i++ ) {
				glVertex2f(convex_hull[i].getX(), convex_hull[i].getY());
			}
			glEnd();
			glDisable (GL_LINE_STIPPLE);


			/* draw the preview rectangle */
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			glColor4f( 1.0f, 1.0f, 1.0f, 0.3f );
			glEnable(GL_BLEND);
			glBegin(GL_POLYGON);
			for ( int i = 0; i < convex_hull.size(); i++ ) {
				glVertex2f(convex_hull[i].getX(), convex_hull[i].getY());
			}
			glEnd();
			glDisable(GL_BLEND);

		} else if ( hull.size() > 0 ) {
			glColor3f(1.0f, 1.0f, 1.0f);
			for ( int i = 0; i < hull.size() - 1; i++ ) {
				glBegin(GL_LINES);
				glVertex2f(hull[i  ].getX(), hull[i  ].getY());
				glVertex2f(hull[i+1].getX(), hull[i+1].getY());
				glEnd();
			}
		}

		glPopMatrix();
		glFlush();
	}
}

void GLLSPGraph::wheelEvent(QWheelEvent *event)
{
	double delta = event->delta() / 120.0 * 0.1;
	if (event->orientation() == Qt::Vertical && zoom + delta > 0) {
		zoom += delta;
		update();
	}
	event->accept();
}

void GLLSPGraph::mousePressEvent(QMouseEvent *event)
{
	lastPos = event->pos();
	if (isInitialized && event->buttons() & Qt::LeftButton) {
		this->setCursor(Qt::SizeAllCursor);
	} else if (isInitialized && event->buttons() & Qt::RightButton) {
		convex_hull.clear();
		hull.clear();
	}
}

void GLLSPGraph::mouseMoveEvent(QMouseEvent *event)
{
	if (isInitialized && event->buttons() & Qt::LeftButton) {
		int dx = lastPos.x() - event->x();
		int dy = lastPos.y() - event->y();
		centerX += dx * scale / zoom;
		centerY -= dy * scale / zoom;
	} else if (isInitialized && event->buttons() & Qt::RightButton) {
		double x = (lastPos.x() * scale + minX) / zoom;
		double y = (maxY - lastPos.y() * scale) / zoom;
		hull.push_back(TSPoint(x,y));
	}
	lastPos = event->pos();
	update();
}

void GLLSPGraph::mouseReleaseEvent (QMouseEvent * event)
{
	this->setCursor(Qt::ArrowCursor);
	GrahamScan()(hull, convex_hull);

	/* now determine which time series objects were selected */
	QVector<TSData*> selectedTSData;
	if (convex_hull.size() > 2) {
		for ( int i = 0; i < projection.size(); ++i ) {
			if ( isInsidePolygon(convex_hull, projection[i]) ) {
				selectedTSData.push_back(data[i]);
			}
		}
	}
	emit selectTSData(selectedTSData);

	updateGL();
}

bool GLLSPGraph::isInsidePolygon(QVector<TSPoint> &polygon, TSPoint &p)
{
	int counter = 0;
	int i;
	double xinters;
	TSPoint p1,p2;
	p1 = polygon[0];
	for( i = 1; i <= polygon.size(); ++i)
	{
		p2 = polygon[i % polygon.size()];
		if (p.y > min(p1.y,p2.y)) {
			if (p.y <= max(p1.y,p2.y)) {
				if (p.x <= max(p1.x,p2.x)) {
					if (p1.y != p2.y) {
						xinters = (p.y-p1.y)*(p2.x-p1.x)/(p2.y-p1.y)+p1.x;
						if (p1.x == p2.x || p.x <= xinters)
							counter++;
					}
				}
			}
		}
		p1 = p2;
	}

	if (counter % 2 == 0) {
		return false;
	} else {
		return true;
	}
}


void GLLSPGraph::on_updateRange (double rangeMinX, double rangeMaxX)
{
	isInitialized = true;
	this->update();
}

void GLLSPGraph::resizeGL(int width, int height)
{
	glViewport(0, 0, width, height);
}

GLLSPGraph::~GLLSPGraph()
{

}
